#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include <glm/mat4x4.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include <assert.h>

#include <stdlib.h>

#include "af_view.h"
#include "logger.h"

#include "graphics.h"

#include "af/shape.h"

#define LOG_TAG "Asteroids"

static const GLchar gVertexShader[] =
    "attribute vec4 vPosition;\n"
	"uniform mat4 viewMatrix;"
    "void main() {\n"
    "  gl_Position = viewMatrix * vPosition;\n"
    "}\n";

static const GLchar gFragmentShader[] =
    "precision mediump float;\n"
    "void main() {\n"
    "  gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
    "}\n";

AFProgram* program = NULL;

GLuint vPositionHandle = 0;
GLuint viewMatrixHandle = 0;

glm::mat4 viewMatrix;

/**
 * Helper function for displaying last error code
 */
static void checkGlError(const char* op)
{
    for (GLint error = glGetError(); error; error = glGetError())
        LOG_INFO("after %s() glError (0x%x)\n", op, error);
}

/**
 * Create shaders and initialize viewport
 * @param w screen width
 * @param h screen height
 * @return success flag
 */
bool setupGraphics(int w, int h)
{
	assert(program == NULL);
	program = new AFProgram();

    LOG_INFO("Setup graphics (%d, %d)", w, h);

    if (!program->initProgram(gVertexShader, gFragmentShader))
    {
        LOG_ERROR("Could not create program.");
        return false;
    }

    glLinkProgram(*program);
    GLint linkStatus = GL_FALSE;
    glGetProgramiv(*program, GL_LINK_STATUS, &linkStatus);
    if (!linkStatus)
    {
        GLint infoLength = 0;
        glGetProgramiv(*program, GL_INFO_LOG_LENGTH, &infoLength);

        if (infoLength > 0)
        {
            GLchar* infoLog = new GLchar[infoLength];
            glGetProgramInfoLog(*program, infoLength, NULL, infoLog);
			LOG_ERROR("Error link program: %s\n", infoLog);
            delete[] infoLog;
        }

    	return false;
    }

    vPositionHandle = glGetAttribLocation(*program, "vPosition");
    checkGlError("glGetAttribLocation");
    LOG_INFO("glGetAttribLocation(\"vPosition\") = %d\n", vPositionHandle);

    viewMatrixHandle = glGetUniformLocation(*program, "viewMatrix");
    checkGlError("glGetUniformLocation");
    LOG_INFO("glGetUniformLocation(\"viewMatrix\") = %d\n", viewMatrixHandle);

    if (w == h)
    	viewMatrix = glm::mat4(1.0f);
    else if (w > h)
    	viewMatrix = glm::scale(glm::mat4(1.0f), glm::vec3(1.0f, (float)w / (float)h, 1.0f));
    else
    	viewMatrix = glm::scale(glm::mat4(1.0f), glm::vec3((float)h / (float)w, 1.0f, 1.0f));

    glViewport(0, 0, w, h);
    checkGlError("glViewport");

    return true;
}

AFSpaceship sh;

/**
 * Render frame
 */
void renderFrame()
{
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

    glUseProgram(*program);

    glVertexAttribPointer(vPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, sh.getVertices());
    glEnableVertexAttribArray(vPositionHandle);
    glUniformMatrix4fv(viewMatrixHandle, 1, GL_FALSE, &viewMatrix[0][0]);
    glDrawArrays(GL_LINE_LOOP, 0, sh.getVerticesCount());
}

/**
 * Destroy context
 */
void destroyContext()
{
	if (program && program->isInitialized())
	{
		delete program;
		program = NULL;
	}
}

JNIEXPORT void JNICALL Java_com_af_AFView_nativeSurfaceChanged(JNIEnv * env, jobject obj,  jint width, jint height)
{
    setupGraphics(width, height);
}

JNIEXPORT void JNICALL Java_com_af_AFView_nativeDrawFrame(JNIEnv * env, jobject obj)
{
    renderFrame();
}

JNIEXPORT void JNICALL Java_com_af_AFView_nativeDestroyContext(JNIEnv * env, jobject obj)
{
	destroyContext();
}
